Sorting out some pain points with kickstart

In the last post, I used kickstart.nvim to set up a minimal Rust IDE, in preparation for Advent of Code this year. Then I said I'd leave the topic be until December, unless I can't wait and continue work on backfilling all 200 solutions for past years.

Well, I did the latter, which made clear some pain points for me, including the stuff I listed at the end last time. Some new items joined the list since, mostly very practical editing-related ones. So, what have I done to the config?

Open points from the previous post

go through all plugins included in kickstart.nvim; learn about them, or prune them

I haven't quite done all of them, but most of them.

Adds the :Git command which is kind of like :!git (the raw command line invocation) but slightly nicer, output-wise. :Git itself also opens a view (similar to netrw) where I can individually stage files, see changes in them, etc. It's very nice. It stays, since I've been actively using it.

Githhub extension for the previous one. I've not used it, and I rarely use Github in general (all my repos I host myself, at most with a secondary github origin I remember to push to occasionally). If Github keeps pushing into AI, I may end up never using this plugin[1]. But for now, it stays.

Automatically adjusts tab width and the like to match the file you're editing. I think. Seems like a minor quality of life thing, I'll keep it.

All of the lsp stuff

neovim/nvim-lspconfig, williamboman/mason.nvim, williamboman/mason-lspconfig.nvim, j-hui/fidget.nvim, folke/neodev.nvim
I did modify a couple settings in lua/lsp-config.lua, which mostly have to do with these, but I've done minimal reading about them so far. Going in-depth will probably require a whole post to themselves.

All of the autocomplete stuff

hrsh7th/nvim-cmp, L3MON4D3/LuaSnip, saadparwaiz1/cmp_luasnip, hrsh7th/cmp-nvim-lsp, rafamadriz/friendly-snippets
As above. I have a reasonable idea of the general role they fill, and I've modified some config (more about that further down!), but I've not really read much into any of these.

Absolute life-saver. When you start a multi-key sequence, a cheatsheet pops up that shows you what can come next. Useful for exploratory use, useful for getting used to new keybinds, overall just useful. I don't know if I'll keep it forever, but for now it's essential to me.

Does little marks to the left of each line if they're added or changed. Relatively small thing, and I don't rely on it often, but I'd miss it if it was gone. Stays.

A theme. I'll probably replace it eventually, but for now I don't have the energy to go shopping for themes.

A better status line. I turned it off once to compare, and it is so much prettier. I briefly skimmed the docs, and it seems you can customize what gets shown. I'm very happy with it is right now, but knowing what my options are here would be cool down the line.

Adds indentation guides. Integrates with treesitter and your LSP to highlight the current scope. I like it.

Adds a binding for gc which lets you comment-toggle things. Integrates with all the motions and text objects as you'd expect, and it uses the active LSP to pick the right characters to make things commments. Actively used this already.

Using Obsidian[2], I already got used to not having a file explorer view open. I prefer opening a file searcher, typing in part of the name I'm looking for, and picking the one I meant from a narrowed-down list.

Telescope is that, but much more versatile. It lets me fuzzy find files or open buffers by name (though I had to add a keybind for the latter on my own), it integrates neatly with git by providing a command to grep from the root of the repository, and it lets me search for symbols in the workspace of the active LSP, with the same fuzzy-finding superpowers.

It's hard to understate just how much I rely on this plugin to do anything now. Only thing I use netrw (the built-in file explorer) for sometimes is creating new files.

So yeah, this thing parses a document in to an AST, essentially. A bunch of the previous plugins use that information to work right. But it's not just a dependency: it also provides a bunch of really useful code-oriented textobjects, like function or parameter or statement, which I've been using a bunch.


So yeah, that's all the plugins that came with it by default. I kind of skipped the two biggest chunks (lsp and cmp), but nonetheless I'd say I was rather thorough. I was surprised that (almost) all of the plugins were of immediate use to me—though some only because I took the time to read what they do.

enable format-on-save for code files

In lua/lsp-config.lua, there's an on_attach handler that gets used on every buffer that has an associated LSP. I just added this bit to the end of it:

-- Create a command `:ToggleFormatOnSave` local to the LSP buffer
local autoformat_enabled = true
vim.api.nvim_create_user_command('ToggleFormatOnSave', function()
  autoformat_enabled = not autoformat_enabled
  print('Format on save: ' .. tostring(autoformat_enabled))
end, {})

vim.api.nvim_create_autocmd('BufWritePre', {
  buffer = bufnr,
  callback = function()
    if autoformat_enabled then
      vim.lsp.buf.format {
        async = false,
      }
    end
  end
})

kickstart.nvim has an autoformat plugin, but it did too much, covering use cases that don't apply to me—so I just scavenged the parts that interest me. Writing both a :Command and an autocmd was nice. I'm not sure I'll need :ToggleFormatOnSave, but eh. I'll be glad to have it when I do need it.

use cargo clippy to produce diagnostics

My hunch was I could just configure it on the servers list in lua/lsp-config.lua, and I was right:

rust_analyzer = {
  ["rust-analyzer"] = {
    checkOnSave = {
      command = "clippy",
    },
  },
},

Not much else to say here. I love clippy.

review motions so I don't hold j/k to navigate through files

I still do that, but I turned on relativenumber, so now my line numbers are relative, which helps with picking the right number for number-prefixed motions. I now know that [ and ] are a thing, and with the help of which-key, I'm slowly getting used to using them more.

The main thing that I need to look up now is how to scroll the screen without going to the edge with my cursor. I think it's also possible to configure a scroll-off threshold or something like that? So it starts scrolling before I reach the very top or bottom. I'll note that down for next time.

New stuff that was annoying me

By actually using the editor to code, I noticed a couple things that were annoying me.

Solved

Some of them I immediately solved, either because they were very low-hanging fruit, or got in my way that much.

Autocompletion too eager

There's plenty of snippets I have access to from various preconfigured snippet sources. That means I frequently get offered them, which usually isn't a problem. I'm not that bothered by a dropdown following my cursor.

However, they way it was configured, Enter would accept the first item in the list, even if I hadn't selected anything. Particularly when writing comments, I would often accidentally mangle it by inserting a snippet that's partially comment:

Pasted image 20231112174219.png

Pressing Enter now would give me this:

Pasted image 20231112174252.png

Obvious nonsense! I thought I would have to mess with the Enter keybind (and in a way I was right), but reading some more docs, turned out that's simply a setting. lua/lsp-config.lua also has config for all the autocompletion stuff, and therein lay the culprit:

['<CR>'] = cmp.mapping.confirm {
  behavior = cmp.ConfirmBehavior.Replace,
  select = true,
},

Taken from the mapping table in the call to cmp.setup({...}). The select here makes it so that if nothing was selected, it automatically selects the first item in the displayed autocompletion list. Which is the exact thing that's been bothering me! Changed it to false, and now snippets only happen if I pick something. Good improvement.

No good way to select an entire control flow statement with its block

For moving, copying, deleting, whatever. It would make sense for this to be a text object, and since treesitter is the thing that provides code-related text objects, I read its docs a bit more, and found that it indeed has one, called @statement.outer.

textobjects = {
      select = {
        ...
        keymaps = {
          -- You can use the capture groups defined in textobjects.scm
          ['aa'] = '@parameter.outer',
          ['ia'] = '@parameter.inner',
          ['af'] = '@function.outer',
          ['if'] = '@function.inner',
          ['ac'] = '@class.outer',
          ['ic'] = '@class.inner',
          ['ae'] = '@statement.outer',
        },
      },
      move = {
        enable = true,
        set_jumps = true, -- whether to set jumps in the jumplist
        goto_next_start = {
          [']m'] = '@function.outer',
          [']]'] = '@class.outer',
          [']e'] = '@statement.outer',
        },
        goto_next_end = {
          [']M'] = '@function.outer',
          [']['] = '@class.outer',
          [']E'] = '@statement.outer',
        },
        goto_previous_start = {
          ['[m'] = '@function.outer',
          ['[['] = '@class.outer',
          ['[e'] = '@statement.outer',
        },
        goto_previous_end = {
          ['[M'] = '@function.outer',
          ['[]'] = '@class.outer',
          ['[E'] = '@statement.outer',
        },
      },
      ...
    }

Added a keybind for it (ae, as in outer statement... well, mostly because e is easy to access and free), as well as ]e, ]E, [e, and [E , to navigate to the beginning/end of the next/previous statement. Problem solved!

Can't say I understand all of it, but the keybinds work!

Low-hanging fruit

While perusing the configs, I found the list of treesitter grammars to install (it's called ensure_installed in lua/treesitter-config.lua). Removed the ones I don't care about, like cpp, javascript, typescript, etc; and added markdown instead. I have a feeling I might need that one soon.

Unsolved

Pairing delimiters

I'm used to the behaviour that typing an opening delimiter automatically places the matching closing delimiter, too; and then, typing a closing delimiter when they're balanced simply passes over it instead of adding an additional one.

This is such a common thing that I'm sure there's a plugin for it, I just haven't gotten around to finding it yet.

Writing into an empty block

This may be a pecularity of rust-fmt, but I occasionally run into this situation:

if condition {}

It loves putting empty blocks on the same line like that. Now, how do I start writing stuff into it? I can navigate to the {, press o, and now it looks like this:

if condition {
typing here}

I drag the closing brace with me whilst typing. Annoying! I would expect it to become this:

if condition {
	typing here
}

I'm hoping that when I get around to getting a delimiter plugin, that'll also be solved.

Diagnostics listing only shows problems in current file

I think that's a decent default behaviour, but I haven't found a way to show a full diagnostics list for the entire workspace yet. It's neceessary sometimes, especially during refactors.

LSP action rename doesn't save files

This is fine when you're renaming something small within the current scope, but with larger workspace-wide renames, that can screw you real quick. I assume this is ignorance on my part, and there's a way to do that.

Scroll-off threshold

I hate having to scroll to the very edge before the view starts scrolling. Should be like a quarter of the screen off the edge. I think this is just a simple setting, I should just do it.

Scrolling without moving cursor (too much)

From doing vim-tutor ages ago, I remember there's buttons for it. I need to look them up, lmao.

Summary

Overall, I'm feeling quite good with my config-fu by now. Adding a whole text object to my repertoire? That's not basic config stuff.

I'm also feeling more and more at home in Neovim in general. I think modal editing meshes well with how I think in the first place.

I still haven't really added any new plugins of my own yet, but I've been eyeing simrat39/rust-tools.nvim. I may try setting it up properly next time.


  1. Mildly ironic, considering all my plugins come from Github. Oh well. ↩︎

  2. Speaking of Obsidian, I am now reaching that point where Vim controls are slowly overwriting my previous workflow, so I'll try to enter insert mode in Obsidian or Ctrl+W l to a different window.......... ↩︎